﻿using System;
using System.Threading.Tasks;
using com.IronOne.BoardPACWinAppBO.General;
using com.IronOne.BoardPACWinAppBO.Paper;
using com.IronOne.BoardPACWinAppService.Auth;
using com.IronOne.BoardPACWinAppService.Sync;
using com.IronOne.BoardPACWinAppUtil.Enums;
using com.IronOne.BoardPACWinAppUtil.Util;
using GalaSoft.MvvmLight.Messaging;

namespace com.IronOne.BoardPACWinApp.Sync
{
    /// <summary>
    ///     Sync Manager will initiate Synchronization betwen the app and the backend
    ///     Ex: http://172.21.21.23/3tierweb2/DownloadService/DownloadService?fmt=xml& fmt1=bml
    ///     Sync should execute in this order: DataUpload | DocumentsUpload | DataDownload | DocumentsDownload
    ///     This should initiate on a successful login ONLY
    ///     Data: Everything on the database
    ///     Documents: Pdf's and other files
    /// </summary>
    internal class SyncManager
    {
        public delegate void PaperDownloadOperationDel(PaperDownloadStatuses status, int paperId);

        private static SyncManager _syncManager;

        /// <summary>
        ///     Used minutes here. Default is 1
        /// </summary>
        private static int _syncTimer = -1;

        /// <summary>
        ///     Indication of sync running status
        ///     If True sync is running ok
        /// </summary>
        private static bool _isNormalSyncRunning;

        /// <summary>
        ///     Indication of sync running status
        ///     If True sync is running ok
        /// </summary>
        private static bool _isInstantSyncRunning;

        private static bool _isDataUploadRunning = true;
        private static bool _isDataDownloadRunning = true;
        private static bool _isDocumentDownloadRunning = true;
        private readonly IAuthService _authService;
        private readonly ISyncService _syncService;

        /// <summary>
        ///     5 Seconds
        /// </summary>
        private const int DbCheckDelay = 5000;

        /// <summary>
        /// </summary>
        private const int MaxRetryCount = 2;

        /// <summary>
        ///     Count == 5
        /// </summary>
        private const int MaxSyncErrorCount = 5;

        private SyncManager()
        {
            _syncService = new SyncService();
            _authService = new AuthService();
        }

        public static event PaperDownloadOperationDel GetPaperDownloadOperationStatusEvt;

        public static SyncManager GetInstance()
        {
            return _syncManager ?? (_syncManager = new SyncManager());
        }

        private void SendMessage(MessageDataModel mdm, string code)
        {
            var message = new NotificationMessage<MessageDataModel>(mdm, code);
            Messenger.Default.Send(message);
        }

        public void StartSync()
        {
            StartInstantSync();
            StartNormalSync();

            _isDataUploadRunning = true;
            _isDataDownloadRunning = true;
            _isDocumentDownloadRunning = true;
        }

        public static void StopSync()
        {
            _isInstantSyncRunning = false;
            _isNormalSyncRunning = false;
            _syncTimer = -1;
        }

        public static bool IsSyncStopped()
        {
            try
            {
                if (!_isDataUploadRunning || !_isDataDownloadRunning || !_isDocumentDownloadRunning)
                    return true;
                return false;
            }
            catch (Exception)
            {
                return false;
            }
        }

        private static void StopInstantSync()
        {
            _isInstantSyncRunning = false;
        }

        private static void StopNormalSync()
        {
            _isNormalSyncRunning = false;
        }

        private async void StartInstantSync()
        {
            try
            {
                //Make sure sync will not execute twice
                if (_isInstantSyncRunning) return;
                _isInstantSyncRunning = true;

                var dataUploadErrorCountX = 0;
                var dataDownloadErrorCountX = 0;
                var logRecordCountErr = 0;

                while (_isInstantSyncRunning && Global.IsOnline && !Global.UserForceWentOffline)
                {
                    _isDataUploadRunning = true;
                    _isDataDownloadRunning = true;
                    _isDocumentDownloadRunning = true;

                    var logData = await _syncService.GetLogRecords();
                    if (logData != null && logData.Count > 0)
                    {
                        var logRecordCount = logData.Count;

                        #region In case of falty records, this will increase efficincy

                        if (dataUploadErrorCountX >= MaxRetryCount || dataDownloadErrorCountX >= MaxRetryCount)
                        {
                            if (logRecordCountErr < logRecordCount)
                            {
                                logRecordCountErr = logRecordCount;
                                dataUploadErrorCountX = 0;
                                dataDownloadErrorCountX = 0;

                                StopNormalSync();
                                //Let the upload and download happens, but normal sync need to stop
                            }
                            else
                            {
                                await Task.Delay(DbCheckDelay);
                                continue;
                            }
                        }

                        #endregion

                        var isDataUploadSuccessX = false;
                        var isDataDownloadSuccessX = false;

                        try
                        {
                            if (_isInstantSyncRunning) isDataUploadSuccessX = await DataUpload();
                            if (isDataUploadSuccessX) dataUploadErrorCountX = 0;
                            else dataUploadErrorCountX++;
                        }
                        catch (Exception)
                        {
                            isDataUploadSuccessX = false;
                            dataUploadErrorCountX++;
                        }
                        try
                        {
                            if (isDataUploadSuccessX) //NOTE: If upload success only
                            {
                                if (_isInstantSyncRunning) isDataDownloadSuccessX = await DataDownload();
                                if (isDataDownloadSuccessX) dataDownloadErrorCountX = 0;
                                else dataDownloadErrorCountX++;
                            }
                        }
                        catch (Exception)
                        {
                            dataDownloadErrorCountX++;
                        }

                        if (isDataDownloadSuccessX)
                        {
                            SendMessage(
                                new MessageDataModel {MessageData = "Sync data download completed successfully"},
                                MessageCodes.SYNC_DATA_DOWNLOAD_SUCCESS);
                        }

                        if(_isInstantSyncRunning && _isNormalSyncRunning)
                            StartSync();
                    }

                    if (!_isInstantSyncRunning) break;

                    await Task.Delay(DbCheckDelay);
                } // End While

                if (!Global.IsOnline || Global.UserForceWentOffline)
                {
                    StopInstantSync();
                    //No need to send any message - Suren 2015-03-31
                    //SendMessage(new MessageDataModel() { MessageData = "Sync Stopped due to offline mode" }, MessageCodes.SYNC_ERROR_STOP);
                }
            }
            catch (Exception e)
            {
                StopInstantSync();
                SendMessage(
                    new MessageDataModel
                    {
                        MessageData = "Sync general error 3!\n\n" + e.Message + "\n\nstack: " + e.StackTrace
                    },
                    MessageCodes.SYNC_ERROR_STOP);
            }
            finally
            {
                _isDataUploadRunning = false;
                _isDataDownloadRunning = false;
                _isDocumentDownloadRunning = false;
            }
        }

        private async void StartNormalSync()
        {
            try
            {
                //Make sure sync will not execute twice
                if (_isNormalSyncRunning) return;
                _isNormalSyncRunning = true;

                var dataUploadErrorCount = 0;
                var dataDownloadErrorCount = 0;
                var documentsDownloadErrorCount = 0;

                while (_isNormalSyncRunning && Global.IsOnline && !Global.UserForceWentOffline)
                {
                    try
                    {
                        _isDataUploadRunning = true;
                        _isDataDownloadRunning = true;
                        _isDocumentDownloadRunning = true;

                        //In case syncIsRunning becomes false due to execution of StopSync() or an exception
                        //below code will terminate the sync
                        if (!_isNormalSyncRunning) break;

                        #region Retriving sync timer (If fails 1 min will be used as default)

                        await GetSyncTimer();

                        #endregion

                        var isDataUploadSuccess = false;
                        var isDataDownloadSuccess = false;
                        var isDocumentsDownloadSuccess = false;

                        try
                        {
                            if (_isNormalSyncRunning) isDataUploadSuccess = await DataUpload();
                            if (isDataUploadSuccess) dataUploadErrorCount = 0;
                            else dataUploadErrorCount++;
                        }
                        catch (Exception)
                        {
                            dataUploadErrorCount++;
                        }


                        try
                        {
                            if (_isNormalSyncRunning) isDataDownloadSuccess = await DataDownload();
                            if (isDataDownloadSuccess) dataDownloadErrorCount = 0;
                            else dataDownloadErrorCount++;
                        }
                        catch (Exception)
                        {
                            dataDownloadErrorCount++;
                        }


                        if (isDataDownloadSuccess)
                        {
                            SendMessage(
                                new MessageDataModel {MessageData = "Sync data download completed successfully"},
                                MessageCodes.SYNC_DATA_DOWNLOAD_SUCCESS);
                        }
                        if (!isDataDownloadSuccess)
                        {
                            SendMessage(new MessageDataModel {MessageData = "Sync data download failed"},
                                MessageCodes.SYNC_DATA_DOWNLOAD_FAIL);
                        }


                        try
                        {
                            if (_isNormalSyncRunning) isDocumentsDownloadSuccess = await DocumentsDownload();
                            if (isDocumentsDownloadSuccess) documentsDownloadErrorCount = 0;
                            else documentsDownloadErrorCount++;
                        }
                        catch (Exception)
                        {
                            documentsDownloadErrorCount++;
                        }


                        //Error
                        if (dataUploadErrorCount > MaxSyncErrorCount || dataDownloadErrorCount > MaxSyncErrorCount ||
                            documentsDownloadErrorCount > MaxSyncErrorCount)
                        {
                            StopNormalSync();
                            SendMessage(new MessageDataModel
                            {
                                MessageData = "Sync interrupted DataUploadErrorCount:" + dataUploadErrorCount
                                              + " DataDownloadErrorCount:" + dataDownloadErrorCount +
                                              " DocumentsDownloadErrorCount:" + documentsDownloadErrorCount
                            }, MessageCodes.SYNC_ERROR_STOP);
                            break;
                        }

                        if (isDataUploadSuccess && isDataDownloadSuccess && isDocumentsDownloadSuccess)
                        {
                            SendMessage(new MessageDataModel {MessageData = "Sync completed successfully"},
                                MessageCodes.SYNC_SUCCESS);
                        }

                        if (!_isNormalSyncRunning) break;
                        await Task.Delay(_syncTimer*60*1000);
                    }
                    catch (Exception e)
                    {
                        StopNormalSync();
                        SendMessage(
                            new MessageDataModel
                            {
                                MessageData = "Sync general error 1!\n\n" + e.Message + "\n\nstack: " + e.StackTrace
                            },
                            MessageCodes.SYNC_ERROR_STOP);
                        break;
                    }
                } // End While

                if (!Global.IsOnline || Global.UserForceWentOffline)
                {
                    StopNormalSync();

                    var mdm = new MessageDataModel { MessageData = false };
                    var message = new NotificationMessage<MessageDataModel>(mdm, MessageCodes.IS_ONLINE);
                    Messenger.Default.Send(message);
                }
            }
            catch (Exception e)
            {
                StopNormalSync();
                SendMessage(
                    new MessageDataModel
                    {
                        MessageData = "Sync general error 2!\n\n" + e.Message + "\n\nstack: " + e.StackTrace
                    },
                    MessageCodes.SYNC_ERROR_STOP);
            }
            finally
            {
                _isDataUploadRunning = false;
                _isDataDownloadRunning = false;
                _isDocumentDownloadRunning = false;
            }
        }

        private async Task GetSyncTimer()
        {
            try
            {
                if (_syncTimer == -1) // -1 means 1st time, so read it from db
                {
                    var syFreq =
                        await
                            _authService.GetSettingValueBySettingName(DatabaseType.GeneralDb,
                                ApplicationConstants.GeneralDbSettingNames.SyncFrequencySelected);
                    _syncTimer = Convert.ToInt32(syFreq);

                    if (_syncTimer == -1) // -1 means Server Decides
                    {
                        var syFreqSvrDec =
                            await
                                _authService.GetSettingValueBySettingName(DatabaseType.GeneralDb,
                                    ApplicationConstants.GeneralDbSettingNames.SyncFrequencyServerDecides);
                        _syncTimer = Convert.ToInt32(syFreqSvrDec);
                        _syncTimer = (_syncTimer/60);
                    }
                }
            }
            catch (Exception)
            {
                _syncTimer = 1;
            }
        }

        #region Upload

        private async Task<bool> DataUpload()
        {
            try
            {
                _isDataUploadRunning = true;
                var result = await _syncService.DataUpload();
                return !(result.IsError);
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                if (!_isInstantSyncRunning && !_isNormalSyncRunning)
                    _isDataUploadRunning = false;
            }
        }

        #endregion

        #region DataDownload

        private async Task<bool> DataDownload()
        {
            try
            {
                _isDataDownloadRunning = true;
                var result = await _syncService.DataDownload();
                return !(result.IsError);
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                if (!_isInstantSyncRunning && !_isNormalSyncRunning)
                    _isDataDownloadRunning = false;
            }
        }

        #endregion

        #region DocumentsDownload

        private async Task<bool> DocumentsDownload()
        {
            try
            {
                _isDocumentDownloadRunning = true;
                var result1 = await DocumentBulkDownload();
                var result2 = await AnnotationBulkDownload();

                if (result1.IsError || result2.IsError) return false;
                return true;
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                if (!_isInstantSyncRunning && !_isNormalSyncRunning)
                    _isDocumentDownloadRunning = false;
            }
        }

        private async Task<ResponseDataModel> DocumentBulkDownload()
        {
            try
            {
                var responseModel = new ResponseDataModel();
                var papersList = await _syncService.GetPapersToDownload();

                foreach (var paper in papersList)
                {
                    //NOTE: User manually stops sync so, paper download will be halted
                    if (!_isInstantSyncRunning && !_isNormalSyncRunning)
                    {
                        responseModel.IsError = false;
                        responseModel.ErrorMessage = "Sync stopped";
                        break;
                    }
                    if (Global.IsOnAgendaPage)
                    {
                        GetPaperDownloadOperationStatusEvt(PaperDownloadStatuses.Downloding, paper.PaperId);
                    }

                    //await Task.Run(() => ComputeNextMove(paper.RefId, paper.DocType, paper.PaperId, paper.VersionId));
                    var paper1 = paper;
                    responseModel =
                        await
                            Task.Run(
                                () =>
                                    ComputeDocumentDownload(paper1.RefId, paper1.DocType, paper1.PaperId, paper1.VersionId));

                    if (Global.IsOnAgendaPage)
                    {
                        GetPaperDownloadOperationStatusEvt(PaperDownloadStatuses.Downloded, paper.PaperId);
                    }

                    if (responseModel.IsError && Global.IsOnAgendaPage)
                    {
                        GetPaperDownloadOperationStatusEvt(PaperDownloadStatuses.DownloadError, paper.PaperId);
                    }
                }
                return responseModel;
            }
            catch (Exception)
            {
                throw;
            }
        }

        private async Task<ResponseDataModel> ComputeDocumentDownload(int refId, byte docType, int paperId,
            int versionId)
        {
            try
            {
                return await _syncService.DocumentDownload(refId, docType, paperId, versionId);
            }
            catch (Exception)
            {
                throw;
            }
        }


        private async Task<ResponseDataModel> AnnotationBulkDownload()
        {
            try
            {
                var responseModel = new ResponseDataModel();
                var annotFileList = await _syncService.GetAnnotationFilesToDownload();

                foreach (var annotFile in annotFileList)
                {
                    //NOTE: User manually stops sync so, annotation download will be halted
                    if (!_isInstantSyncRunning && !_isNormalSyncRunning)
                    {
                        responseModel.IsError = false;
                        responseModel.ErrorMessage = "Sync stopped";
                        break;
                    }

                    var file = annotFile;
                    responseModel = await Task.Run(() => ComputeAnnotationFileDownload(file));
                }
                return responseModel;
            }
            catch (Exception)
            {
                throw;
            }
        }

        private async Task<ResponseDataModel> ComputeAnnotationFileDownload(PaperModel annotModel)
        {
            try
            {
                return await _syncService.AnnotationDownload(annotModel);
            }
            catch (Exception)
            {
                throw;
            }
        }

        #endregion
    }
}